package edu.gatech.cs2340.teamrocket.control.networking.node;

import edu.gatech.cs2340.teamrocket.control.networking.dialog.Dialog;
import edu.gatech.cs2340.teamrocket.control.networking.exception.TimeoutException;
import edu.gatech.cs2340.teamrocket.control.networking.message.Message;
import edu.gatech.cs2340.teamrocket.control.networking.messagehandler.MessageHandler;

/**
 * Message acceptors and senders. Can either be a server or a client.
 *
 * @author Joe Tacheron
 */
public abstract class Node {

    public static int portNum = 4444;

    /**
     * This will handle all incoming messages
     */
    protected MessageHandler handler;
    /**
     * The message counter.
     */
    protected long messageCount;

    private boolean running;
    private volatile boolean waiting; // TODO: move this to Dialog
    // Message number this is waiting for
    private long messageID; // TODO: move this to Dialog

    private Message reply;

    public Node() { }

    /**
     *
     * @param handler the default message handler
     */
    public Node(MessageHandler handler){
        this.handler = handler;
    }

    /**
     * Have a Dialog send a message. Increment messageCount.
     *
     * @param d
     * @param m
     */
    public void send(Dialog d, Message m){
        d.send(m);
        messageCount++;
    }

    /**
     * Send a message and return after a response is received.
     *
     * @param d The Dialog to send the Message.
     * @param m The Message to send.
     *
     * @return The Message received.
     *
     * @throws TimeoutException If the server did not respond timely.
     */
    public synchronized Message sendAndWait(Dialog d, Message m) throws TimeoutException {
        if(waiting)
            throw new RuntimeException("Already waiting on input");

        reply = null;
        waiting = true;
        messageID = messageCount;
        d.send(m);

        // timeout
        new Thread(new Runnable(){
            @Override
            public void run(){
                try{
                    Thread.sleep(5000);
                }
                catch(InterruptedException e) { }
                Node.this.waiting = false;
                synchronized(Node.this){
                    Node.this.notify();
                }
            }
        }).start();

        while(waiting)
            try {
                wait();
            }
            catch (InterruptedException ignore) { }

        if(reply == null)
            throw new TimeoutException();

        messageCount++;
        notify();
        return reply;
    }

    /**
     * Handle a message. If this Node is waiting for this Message, it will be
     * set as the reply field, which will be accessed by sendAndWait().
     * Otherwise, if there is MessageHandler, it will be ignored. If there
     * is a MessageHandler, it will handle the Message.
     *
     * @param d The originating Dialog.
     * @param m The received Message.
     */
    public synchronized void handleMessage(Dialog d, Message m){
        if(waiting && m.getMessageID() == messageID){
            reply = m;
            waiting = false;
            notify();
        }
        if(handler == null){
            System.out.println("Message reieced and ignored.");
        }
        else{
            handler.handleMessage(this, d, m);
        }
    }

    /**
     * If this Node is running.
     *
     * @return
     */
    public boolean isRunning(){
        return running;
    }

    /**
     * Set the running state.
     *
     * @param b new running state.
     */
    protected void setRunning(boolean b){
        running = b;
    }

    /**
     * Set the port number that all networking classes will operate on.
     *
     * @param pn the new port number.
     */
    public static void setPortNum(int pn){
        portNum = pn;
    }

    /**
     *
     * @return the port number. The default is 4444.
     */
    public static int getPortNum(){
        return portNum;
    }
}
